/*!
\copyright  Copyright (c) 2008 - 2018 Qualcomm Technologies International, Ltd.\n
            All Rights Reserved.\n
            Qualcomm Technologies International, Ltd. Confidential and Proprietary.
\version    Part of 6.3.0
\file       av_headset_charger.c
\brief      Charger monitoring
*/

#include "panic.h"

#include "av_headset.h"
#include "av_headset_ui.h"
#include "av_headset_charger.h"
#include "av_headset_log.h"
#include "av_headset_temperature.h"

/**************************************************************************/
/*! /brief Check if we KNOW that the system can power off.

    This function should only be called if the charger is enabled (should
    always be), and checks if there is any power detected.

    If the power status is not known, the function assumes that we cannot 
    power off. An example would be a charger status of DISABLED_ERROR (see
    charger_if.h).
    
    \note If charger support has not been included, then this function
    will return FALSE as the system behaviour is not known/detectable.

    \returns TRUE if it is certain there is no external power
 */
bool appChargerCanPowerOff(void)
{
#ifdef INCLUDE_CHARGER
    switch (ChargerStatus())
    {
        case NO_POWER:
            return TRUE;

        default:
            return FALSE;
    }
#else
    /* Assume we can't turn off */
    return FALSE;
#endif
}

/**************************************************************************/
/*! \brief Check if a charger is connected

    \note   The value returned if the charger is disabled is indeterminate. 
            This function should only be relied upon if the charger is enabled.

    \note   If there is no charger support in the project, then this 
            function will return TRUE

    \return TRUE if the charger is in a state where we know there is power, 
            FALSE otherwise 
 */
bool appChargerIsConnected(void)
{
#ifdef INCLUDE_CHARGER
    switch (ChargerStatus())
    {
        case TRICKLE_CHARGE:
        case FAST_CHARGE:
        case DISABLED_ERROR: /*! Disabled, but still connected */
        case STANDBY:
        case PRE_CHARGE:
        case HEADROOM_ERROR:
            return TRUE;

            /* Could list all the other cases here, so there is a compilation
                error if the enum is extended... but the type mixes error codes
                with actual state */
        default:
            return FALSE;
    }
#else
    return TRUE;
#endif
}



/* Only compile if CHARGER defined */
#ifdef INCLUDE_CHARGER

/*! Interval at which the charger status is checked */
#define CHARGER_READ_PERIOD (D_SEC(1))

/*! Add reason for the charger to be disabled */
static void appChargerDisableReasonAdd(chargerDisableReason reason);
/*! Clear reason for the charger to be disabled */
static void appChargerDisableReasonClear(chargerDisableReason reason);

/*! Internal message IDs used by the Charger module */
enum av_headset_charger_internal_messages
{
    /*! Periodically poll the charger status */
    CHARGER_INTERNAL_TIMER  = INTERNAL_MESSAGE_BASE + 0x80,
    /*! Used to limit the time spent in some charge phases */
    CHARGER_INTERNAL_CHARGE_TIMEOUT,
};

/**************************************************************************/
static  bool appChargerIsCharging(void)
{
    switch (ChargerStatus())
    {
        case TRICKLE_CHARGE:
        case FAST_CHARGE:
        case PRE_CHARGE:
            return TRUE;
        default:
            return FALSE;
    }
}

#ifdef CHG_FINISH_LED
static void appChargerSendFinish(void)
{
	chargerTaskData *theCharger = appGetCharger();
	MessageCancelAll(&theCharger->task, CHARGER_STATE_FINISH);
	MessageSendLater(&theCharger->task, CHARGER_STATE_FINISH, NULL, D_SEC(30));
}

static void appChargerCancelFinish(void)
{
	chargerTaskData *theCharger = appGetCharger();
	MessageCancelAll(&theCharger->task, CHARGER_STATE_FINISH);
}

static void appChargerExecuteFinish(void)
{
	appChargerCancelFinish();
	appUiChargerFinish();
	appPowerOffRequest();
	/*appChargerForceDisable();*/
	/*PsuConfigure(PSU_ALL, PSU_SMPS_INPUT_SEL_VBAT, 1);*/
	appPowerDoPowerOff();
}

/*==============================================*/
static void appChargerCancelQuickREL(void)
{
	chargerTaskData *theCharger = appGetCharger();
	MessageCancelAll(&theCharger->task, CHARGER_QUICK_RELEASE);
}

static void appChargerSendQuickREL(void)
{
	chargerTaskData *theCharger = appGetCharger();
	appChargerCancelQuickREL();
	MessageSendLater(&theCharger->task, CHARGER_QUICK_RELEASE, NULL, D_SEC(4));
}

static void appChargerHandleQuickREL(void)
{
	appChargerCancelQuickREL();
	if(appSmIsInCase())
	{
		if(appConManagerAnyLinkConnected() || appHfpIsConnected() || appAvHasAConnection())
		{
			UserDisconnectAllLinks();
		}
	}
}

#endif

/**************************************************************************/
static void appChargerEvent(void)
{
    chargerTaskData *theCharger = appGetCharger();
    bool is_connected = appChargerIsConnected();
    bool is_charging = appChargerIsCharging();

    if (theCharger->status != ChargerStatus())
    {
        uint32 charge_timeout_ms = 0;
        theCharger->status = ChargerStatus();
        switch (theCharger->status)
        {
            case TRICKLE_CHARGE:
                DEBUG_LOG("appChargerIsCharging, trickle charge");
                break;
            case FAST_CHARGE:
                DEBUG_LOG("appChargerIsCharging, fast charge");
                charge_timeout_ms = appConfigChargerFastChargeTimeoutMs();
                break;
            case DISABLED_ERROR:
                DEBUG_LOG("appChargerIsCharging, disabled");
                break;
            case STANDBY:
                DEBUG_LOG("appChargerIsCharging, standby");
                break;
            case PRE_CHARGE:
                DEBUG_LOG("appChargerIsCharging, pre charge");
                charge_timeout_ms = appConfigChargerPreChargeTimeoutMs();
                break;
            case NO_POWER:
                DEBUG_LOG("appChargerIsCharging, no power");
                break;
            case HEADROOM_ERROR:
                DEBUG_LOG("appChargerIsCharging, headroom error");
                break;
            default:
                DEBUG_LOGF("appChargerIsCharging, status %d", ChargerStatus());
                break;
        }

        MessageCancelAll(&theCharger->task, CHARGER_INTERNAL_CHARGE_TIMEOUT);
        if (charge_timeout_ms)
        {
            MessageSendLater(&theCharger->task, CHARGER_INTERNAL_CHARGE_TIMEOUT,
                             NULL, charge_timeout_ms);
        }
    }

    /* Check if connected status has changed */
    if (is_connected != theCharger->is_connected)
    {
#ifdef CHG_FINISH_LED
    	appChargerCancelFinish();
	appChargerCancelQuickREL();
#endif
        /* Check if connected */
        if (is_connected)
        {
#ifdef CHG_FINISH_LED
        	appChargerSendQuickREL();
#endif
            /* Indicate charger connection */
            appUiChargerConnected();

            /* Clear charging flag now to kick off charging indication later */
            theCharger->is_charging = FALSE;

            appTaskListMessageSendId(theCharger->client_tasks, CHARGER_MESSAGE_ATTACHED);

            PsuConfigure(PSU_ALL, PSU_SMPS_INPUT_SEL_VBAT, 0);
        }
        else
        {
            /* Indicate charger disconnection */
            appUiChargerDisconnected();
        
            /* Cancel any timers running */
            MessageCancelAll(&theCharger->task, CHARGER_INTERNAL_TIMER);

            /* Clear charging flag */
            theCharger->is_charging = FALSE;

            /* Allow the battery to charge after timeout if the charger is disconnected */
            appChargerDisableReasonClear(CHARGER_DISABLE_REASON_TIMEOUT);

            appTaskListMessageSendId(theCharger->client_tasks, CHARGER_MESSAGE_DETACHED);
        }

        /* Store updated connected state */
        theCharger->is_connected = is_connected;
    }
    
    /* Check if connected */
    if (is_connected)
    {
        /* Check if we have finished charging */
        if (!is_charging && theCharger->is_charging)
    	{
            appUiChargerComplete();
#ifdef CHG_FINISH_LED
		appChargerSendFinish();	
#endif
	}
        else if (is_charging)
        {
            if (appBatteryGetVoltage() > appConfigBatteryVoltageOk())
                appUiChargerChargingOk();
            else
                appUiChargerChargingLow();
        }

        /* Store updated charging status */
        theCharger->is_charging = is_charging;
    }

    /* Start charger poll timer if charger is connected and still charging */
    if (is_connected && is_charging)
    {
        MessageCancelAll(&theCharger->task, CHARGER_INTERNAL_TIMER);
        MessageSendLater(&theCharger->task, CHARGER_INTERNAL_TIMER, 0, CHARGER_READ_PERIOD);
    }
}

/*! Handle a temperature state change */
static void appChargerHandleTemperatureStateChange(const TEMPERATURE_STATE_CHANGED_IND_T *ind)
{
    switch (ind->state)
    {
        case TEMPERATURE_STATE_WITHIN_LIMITS:
            DEBUG_LOG("appChargerHandleTemperatureStateChange, within limits");
            appChargerDisableReasonClear(CHARGER_DISABLE_REASON_EXTREME_TEMPERATURE);
        break;

        case TEMPERATURE_STATE_ABOVE_UPPER_LIMIT:
        case TEMPERATURE_STATE_BELOW_LOWER_LIMIT:
            DEBUG_LOG("appChargerHandleTemperatureStateChange, outside limits");
            appChargerDisableReasonAdd(CHARGER_DISABLE_REASON_EXTREME_TEMPERATURE);
        break;

        default:
            Panic();
        break;
    }
}

/*! Handle a charge timeout */
static void appChargerHandleChargeTimeout(void)
{
    chargerTaskData *theCharger = appGetCharger();
    DEBUG_LOGF("appChargerHandleChargeTimeout, charger_status=%d", theCharger->status);
    appChargerDisableReasonAdd(CHARGER_DISABLE_REASON_TIMEOUT);
}

/**************************************************************************/
static void appChargerHandleMessage(Task task, MessageId id, Message message)
{
    UNUSED(task);
    UNUSED(message);

    switch (id)
    {
        case TEMPERATURE_STATE_CHANGED_IND:
            appChargerHandleTemperatureStateChange(message);
        break;

        case CHARGER_INTERNAL_CHARGE_TIMEOUT:
            appChargerHandleChargeTimeout();
        break;

        default:
            /* Check for charger events */
            appChargerEvent();
        break;
    }
#ifdef CHG_FINISH_LED

    switch (id)
    {
        case CHARGER_STATE_FINISH:
            appChargerExecuteFinish();
            break;

        case CHARGER_QUICK_RELEASE:
            appChargerHandleQuickREL();
            break;

        default:
            break;
    }
#endif	
}

/* Set the configuration of the charger.
 */
static void appChargerConfigureCharger(void)
{
    ChargerConfigure(CHARGER_ENABLE, 0);

    /* Currents */
    ChargerConfigure(CHARGER_TRICKLE_CURRENT, appConfigChargerTrickleCurrent());
    ChargerConfigure(CHARGER_PRE_CURRENT, appConfigChargerPreCurrent());
    ChargerConfigure(CHARGER_FAST_CURRENT, appConfigChargerFastCurrent());
    ChargerConfigure(CHARGER_ITERM_CTRL, appConfigChargerTerminationCurrent());

    /* Voltages */
    ChargerConfigure(CHARGER_PRE_FAST_THRESHOLD, appConfigChargerPreFastThresholdVoltage());
    ChargerConfigure(CHARGER_TERM_VOLTAGE, appConfigChargerTerminationVoltage());

    /* Thresholds/timing */
    ChargerConfigure(CHARGER_STANDBY_FAST_HYSTERESIS, appConfigChargerStandbyFastVoltageHysteresis());
    ChargerConfigure(CHARGER_STATE_CHANGE_DEBOUNCE, appConfigChargerStateChangeDebounce());


    ChargerConfigure(CHARGER_ENABLE, 1);
}

static void appChargerDisableReasonAdd(chargerDisableReason reason)
{
    chargerTaskData *theCharger = appGetCharger();
    if (reason)
    {
        if (!theCharger->disable_reason)
        {
            ChargerConfigure(CHARGER_ENABLE, 0);
        }
        theCharger->disable_reason |= reason;
    }
}

static void appChargerDisableReasonClear(chargerDisableReason reason)
{
    chargerTaskData *theCharger = appGetCharger();
    if (reason && theCharger->disable_reason)
    {
        theCharger->disable_reason &= ~reason;
        if (!theCharger->disable_reason)
        {
            appChargerConfigureCharger();
        }
    }
}

#endif /* INCLUDE_CHARGER */

/*! \brief Make sure the charger is disabled

    This function should be called from power off code to make sure that
    the charger is off.
*/
void appChargerForceDisable(void)
{
#ifdef INCLUDE_CHARGER
    appChargerDisableReasonAdd(CHARGER_DISABLE_REASON_REQUEST);
#endif
}

/*! \brief Restore the charger, if there are no other reasons for the charger
    to be disabled.

    This function should only be called after a call to
    \ref appChargerForceDisable().
*/
void appChargerRestoreState(void)
{
#ifdef INCLUDE_CHARGER
    appChargerDisableReasonClear(CHARGER_DISABLE_REASON_REQUEST);
#endif
}


/*! \brief Initialise the application handling of charger

    This function should be called during application initialisation.
    If charger support is not required, the function ensures that
    the charger is disabled.
*/
#ifdef INCLUDE_CHARGER
void appChargerInit(void)
{
    bool registered;
    chargerTaskData *theCharger = appGetCharger();

    /* Set up task handler & record current charger status */
    theCharger->task.handler = appChargerHandleMessage;
    theCharger->client_tasks = appTaskListInit();
    theCharger->is_connected = FALSE;
    theCharger->is_charging = FALSE;
    theCharger->status = ENABLE_FAIL_UNKNOWN;
    theCharger->disable_reason = CHARGER_DISABLE_REASON_NONE;

    /* Register for charger messages */
    MessageChargerTask(&theCharger->task);

    /* Need to switch charger when temperature is outside battery's operating range.
       TEMPERATURE_STATE_CHANGED_IND will trigger enable/disable of the charger. */
    appChargerDisableReasonAdd(CHARGER_DISABLE_REASON_EXTREME_TEMPERATURE);
    registered = appTemperatureClientRegister(&theCharger->task,
                                              appConfigBatteryChargingTemperatureMin(),
                                              appConfigBatteryChargingTemperatureMax());
    if (!registered)
    {
        /* Could not register for temperature, just enable the charger.
           Should only happen when temperature support is not compiled. */
        appChargerDisableReasonClear(CHARGER_DISABLE_REASON_EXTREME_TEMPERATURE);
    }
}
#endif /* INCLUDE_CHARGER */

bool appChargerClientRegister(Task client_task)
{
#ifdef INCLUDE_CHARGER
    chargerTaskData *theCharger = appGetCharger();
    if (appTaskListAddTask(theCharger->client_tasks, client_task))
    {
        /* Send initial state if charger is attached */
        if (appChargerIsConnected())
        {
            MessageSend(client_task, CHARGER_MESSAGE_ATTACHED, NULL);
        }
        else
        {
            MessageSend(client_task, CHARGER_MESSAGE_DETACHED, NULL);
        }
        return TRUE;
    }
#else
    UNUSED(client_task);
#endif
    return FALSE;
}


void appChargerClientUnregister(Task client_task)
{
#ifdef INCLUDE_CHARGER
    chargerTaskData *theCharger = appGetCharger();
    appTaskListRemoveTask(theCharger->client_tasks, client_task);
#else
    UNUSED(client_task);
#endif
}

